WebCodecsμ μ μ¬λ ₯μ μ΅λν νμ©νμΈμ! VideoFrame planeμ μ¬μ©νμ¬ λΉλμ€ νλ μ λ°μ΄ν°μ μ κ·Όνκ³ μ‘°μνλ λ°©λ²μ λν ν¬κ΄μ μΈ κ°μ΄λμ λλ€. ν½μ ν¬λ§·, λ©λͺ¨λ¦¬ λ μ΄μμ, λΈλΌμ°μ μμμ κ³ κΈ λΉλμ€ μ²λ¦¬λ₯Ό μν μ€μ μ¬μ© μ¬λ‘λ₯Ό μμ보μΈμ.
WebCodecs VideoFrame Plane: λΉλμ€ νλ μ λ°μ΄ν° μ‘μΈμ€ μ¬μΈ΅ λΆμ
WebCodecsλ μΉ κΈ°λ° λ―Έλμ΄ μ²λ¦¬μ ν¨λ¬λ€μ μ νμ μλ―Έν©λλ€. μ΄λ λ―Έλμ΄μ κΈ°λ³Έ κ΅¬μ± μμμ λν μ μμ€ μ‘μΈμ€λ₯Ό μ 곡νμ¬ κ°λ°μκ° λΈλΌμ°μ μμ μ§μ μ κ΅ν μ ν리μΌμ΄μ
μ λ§λ€ μ μλλ‘ ν©λλ€. WebCodecsμ κ°μ₯ κ°λ ₯ν κΈ°λ₯ μ€ νλλ VideoFrame κ°μ²΄μ΄λ©°, κ·Έ μμλ λΉλμ€ νλ μμ μμ ν½μ
λ°μ΄ν°λ₯Ό λ
ΈμΆνλ VideoFrame planeμ΄ μμ΅λλ€. μ΄ κΈμ κ³ κΈ λΉλμ€ μ‘°μμ μν΄ VideoFrame planeμ μ΄ν΄νκ³ νμ©νλ λ°©λ²μ λν ν¬κ΄μ μΈ κ°μ΄λλ₯Ό μ 곡ν©λλ€.
VideoFrame κ°μ²΄ μ΄ν΄νκΈ°
planeμ λν΄ μμΈν μμ보기 μ μ VideoFrame κ°μ²΄ μ체λ₯Ό λ€μ μ΄ν΄λ³΄κ² μ΅λλ€. VideoFrameμ λΉλμ€μ λ¨μΌ νλ μμ λνλ
λλ€. μ¬κΈ°μλ λμ½λ©(λλ μΈμ½λ©)λ λΉλμ€ λ°μ΄ν°μ νμμ€ν¬ν, μ§μ μκ°, ν¬λ§· μ 보μ κ°μ κ΄λ ¨ λ©νλ°μ΄ν°κ° μΊ‘μνλμ΄ μμ΅λλ€. VideoFrame APIλ λ€μκ³Ό κ°μ λ©μλλ₯Ό μ 곡ν©λλ€:
- ν½μ λ°μ΄ν° μ½κΈ°: λ°λ‘ μ΄ λΆλΆμμ planeμ΄ μ¬μ©λ©λλ€.
- νλ μ 볡μ¬: κΈ°μ‘΄
VideoFrameκ°μ²΄λ‘λΆν° μλ‘μ΄ κ°μ²΄λ₯Ό μμ±ν©λλ€. - νλ μ λ«κΈ°: νλ μμ΄ λ³΄μ ν κΈ°λ³Έ 리μμ€λ₯Ό ν΄μ ν©λλ€.
VideoFrame κ°μ²΄λ μΌλ°μ μΌλ‘ VideoDecoderμ μν΄ λμ½λ© κ³Όμ μμ μμ±λκ±°λ, μ¬μ©μ μ§μ νλ μμ λ§λ€ λ μλμΌλ‘ μμ±λ©λλ€.
VideoFrame Planeμ΄λ 무μμΈκ°?
VideoFrameμ ν½μ
λ°μ΄ν°λ μ’
μ’
μ¬λ¬ κ°μ planeμΌλ‘ ꡬμ±λλ©°, νΉν YUVμ κ°μ ν¬λ§·μμ κ·Έλ μ΅λλ€. κ° planeμ μ΄λ―Έμ§μ λ€λ₯Έ κ΅¬μ± μμλ₯Ό λνλ
λλ€. μλ₯Ό λ€μ΄, YUV420 ν¬λ§·μλ μΈ κ°μ planeμ΄ μμ΅λλ€:
- Y (νλ): μ΄λ―Έμ§μ λ°κΈ°(luminance)λ₯Ό λνλ λλ€. μ΄ planeμ νλ°± μ 보λ₯Ό λ΄κ³ μμ΅λλ€.
- U (Cb): μ²μμ°¨ ν¬λ‘λ§(μμ°¨) κ΅¬μ± μμλ₯Ό λνλ λλ€.
- V (Cr): μ μμ°¨ ν¬λ‘λ§(μμ°¨) κ΅¬μ± μμλ₯Ό λνλ λλ€.
RGB ν¬λ§·μ κ²λ³΄κΈ°μλ λ κ°λ¨ν΄ 보μ΄μ§λ§, κ²½μ°μ λ°λΌ μ¬λ¬ planeμ μ¬μ©ν μλ μμ΅λλ€. planeμ μμ κ·Έ μλ―Έλ μ μ μΌλ‘ VideoFrameμ VideoPixelFormatμ λ°λΌ λ¬λΌμ§λλ€.
plane μ¬μ©μ μ₯μ μ νΉμ μμ κ΅¬μ± μμμ ν¨μ¨μ μΌλ‘ μ κ·Όνκ³ μ‘°μν μ μλ€λ κ²μ λλ€. μλ₯Ό λ€μ΄, μμ(U λ° V plane)μ μν₯μ μ£Όμ§ μκ³ νλ(Y plane)λ§ μ‘°μ ν μ μμ΅λλ€.
VideoFrame Plane μ‘μΈμ€: API
VideoFrame APIλ plane λ°μ΄ν°μ μ‘μΈμ€νκΈ° μν΄ λ€μ λ©μλλ₯Ό μ 곡ν©λλ€:
copyTo(destination, options):VideoFrameμ λ΄μ©μ λ€λ₯ΈVideoFrame,CanvasImageBitmapλλArrayBufferViewμ κ°μ λμμ 볡μ¬ν©λλ€.optionsκ°μ²΄λ μ΄λ€ planeμ μ΄λ»κ² 볡μ¬ν μ§ μ μ΄ν©λλ€. μ΄κ²μ΄ plane μ‘μΈμ€λ₯Ό μν μ£Όμ λ©μ»€λμ¦μ λλ€.
copyTo λ©μλμ options κ°μ²΄λ₯Ό μ¬μ©νλ©΄ λΉλμ€ νλ μ λ°μ΄ν°μ λ μ΄μμκ³Ό λμμ μ§μ ν μ μμ΅λλ€. μ£Όμ μμ±μ λ€μκ³Ό κ°μ΅λλ€:
format: 볡μ¬λ λ°μ΄ν°μ μνλ ν½μ ν¬λ§·μ λλ€. μ΄λ μλ³ΈVideoFrameκ³Ό λμΌν ν¬λ§·μΌ μλ μκ³ λ€λ₯Έ ν¬λ§·(μ: YUVμμ RGBλ‘ λ³ν)μΌ μλ μμ΅λλ€.codedWidthλ°codedHeight: λΉλμ€ νλ μμ λλΉμ λμ΄(ν½μ λ¨μ)μ λλ€.layout: λ©λͺ¨λ¦¬μμ κ° planeμ λ μ΄μμμ μ€λͺ νλ κ°μ²΄μ λ°°μ΄μ λλ€. λ°°μ΄μ κ° κ°μ²΄λ λ€μμ μ§μ ν©λλ€:offset: λ°μ΄ν° λ²νΌμ μμ λΆλΆλΆν° plane λ°μ΄ν°μ μμ λΆλΆκΉμ§μ μ€νμ (λ°μ΄νΈ λ¨μ)μ λλ€.stride: planeμ κ° ν μμ λΆλΆ μ¬μ΄μ λ°μ΄νΈ μμ λλ€. μ΄λ ν¨λ©μ μ²λ¦¬νλ λ° μ€μν©λλ€.
YUV420 VideoFrameμ μμ λ²νΌλ‘ 볡μ¬νλ μμ λ₯Ό μ΄ν΄λ³΄κ² μ΅λλ€:
async function copyYUV420ToBuffer(videoFrame, buffer) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
// YUV420μ Y, U, Vμ 3κ° planeμ κ°μ§λλ€
const yPlaneSize = width * height;
const uvPlaneSize = width * height / 4;
const layout = [
{ offset: 0, stride: width }, // Y plane
{ offset: yPlaneSize, stride: width / 2 }, // U plane
{ offset: yPlaneSize + uvPlaneSize, stride: width / 2 } // V plane
];
await videoFrame.copyTo(buffer, {
format: 'I420',
codedWidth: width,
codedHeight: height,
layout: layout
});
videoFrame.close(); // 리μμ€λ₯Ό ν΄μ νλ κ²μ΄ μ€μν©λλ€
}
μ€λͺ :
widthμheightλ₯Ό κΈ°λ°μΌλ‘ κ° planeμ ν¬κΈ°λ₯Ό κ³μ°ν©λλ€. Yλ μ 체 ν΄μλμ΄λ©°, Uμ Vλ μλΈμνλ§(4:2:0)λ©λλ€.layoutλ°°μ΄μ λ©λͺ¨λ¦¬ λ μ΄μμμ μ μν©λλ€.offsetμ κ° planeμ΄ λ²νΌμμ μμνλ μμΉλ₯Ό μ§μ νκ³ ,strideλ ν΄λΉ planeμ λ€μ νμΌλ‘ μ΄λνκΈ° μν΄ κ±΄λλΈ λ°μ΄νΈ μλ₯Ό μ§μ ν©λλ€.formatμ΅μ μ μΌλ°μ μΈ YUV420 ν¬λ§·μΈ 'I420'μΌλ‘ μ€μ λ©λλ€.- μ€μν μ μ, λ³΅μ¬ ν 리μμ€λ₯Ό ν΄μ νκΈ° μν΄
videoFrame.close()λ₯Ό νΈμΆνλ€λ κ²μ λλ€.
ν½μ ν¬λ§·: κ°λ₯μ±μ μΈκ³
ν½μ
ν¬λ§·μ μ΄ν΄νλ κ²μ VideoFrame planeμ λ€λ£¨λ λ° νμμ μ
λλ€. VideoPixelFormatμ μμ μ λ³΄κ° λΉλμ€ νλ μ λ΄μμ μ΄λ»κ² μΈμ½λ©λλμ§λ₯Ό μ μν©λλ€. λ€μμ νν μ ν μ μλ λͺ κ°μ§ ν½μ
ν¬λ§·μ
λλ€:
- I420 (YUV420p): Y, U, V κ΅¬μ± μμκ° λ³λμ planeμ μ μ₯λλ νλ©΄ν(planar) YUV ν¬λ§·μ λλ€. Uμ Vλ μν λ° μμ§ μ°¨μ λͺ¨λμμ 2λ°°λ‘ μλΈμνλ§λ©λλ€. λ§€μ° μΌλ°μ μ΄κ³ ν¨μ¨μ μΈ ν¬λ§·μ λλ€.
- NV12 (YUV420sp): Yκ° νλμ planeμ μ μ₯λκ³ Uμ V κ΅¬μ± μμκ° λ λ²μ§Έ planeμ μΈν°λ¦¬λΈ(interleaved)λλ μ€νλ©΄ν(semi-planar) YUV ν¬λ§·μ λλ€.
- RGBA: λΉ¨κ°, λ Ήμ, νλ, μν κ΅¬μ± μμκ° λ¨μΌ planeμ μ μ₯λλ©°, μΌλ°μ μΌλ‘ κ΅¬μ± μμλΉ 8λΉνΈ(ν½μ λΉ 32λΉνΈ)μ λλ€. κ΅¬μ± μμμ μμλ λ€λ₯Ό μ μμ΅λλ€(μ: BGRA).
- RGB565: λΉ¨κ°, λ Ήμ, νλ κ΅¬μ± μμκ° λ¨μΌ planeμ μ μ₯λλ©°, λΉ¨κ°μ 5λΉνΈ, λ Ήμμ 6λΉνΈ, νλμ 5λΉνΈ(ν½μ λΉ 16λΉνΈ)κ° ν λΉλ©λλ€.
- GRAYSCALE: κ° ν½μ μ λν΄ λ¨μΌ νλ(λ°κΈ°) κ°μΌλ‘ νλ°± μ΄λ―Έμ§λ₯Ό λνλ λλ€.
VideoFrame.format μμ±μ μ£Όμ΄μ§ νλ μμ ν½μ
ν¬λ§·μ μλ €μ€λλ€. planeμ μ κ·ΌνκΈ° μ μ μ΄ μμ±μ λ°λμ νμΈνμμμ€. μ§μλλ ν¬λ§·μ μ 체 λͺ©λ‘μ WebCodecs μ¬μμ μ°Έμ‘°ν μ μμ΅λλ€.
μ€μ μ¬μ© μ¬λ‘
VideoFrame planeμ μ κ·Όνλ©΄ λΈλΌμ°μ μμ κ³ κΈ λΉλμ€ μ²λ¦¬λ₯Ό μν λ€μν κ°λ₯μ±μ΄ μ΄λ¦½λλ€. λͺ κ°μ§ μμλ λ€μκ³Ό κ°μ΅λλ€:
1. μ€μκ° λΉλμ€ ν¨κ³Ό
VideoFrameμ ν½μ
λ°μ΄ν°λ₯Ό μ‘°μνμ¬ μ€μκ° λΉλμ€ ν¨κ³Όλ₯Ό μ μ©ν μ μμ΅λλ€. μλ₯Ό λ€μ΄, RGBA νλ μμ κ° ν½μ
μ λν R, G, B κ΅¬μ± μμμ νκ· μ ꡬν λ€μ μΈ κ΅¬μ± μμ λͺ¨λλ₯Ό ν΄λΉ νκ· κ°μΌλ‘ μ€μ νμ¬ νλ°± νν°λ₯Ό ꡬνν μ μμ΅λλ€. μΈνΌμ ν€ ν¨κ³Όλ₯Ό λ§λ€κ±°λ λ°κΈ° λ° λλΉλ₯Ό μ‘°μ ν μλ μμ΅λλ€.
async function applyGrayscale(videoFrame) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
const buffer = new ArrayBuffer(width * height * 4); // RGBA
const rgba = new Uint8ClampedArray(buffer);
await videoFrame.copyTo(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height
});
for (let i = 0; i < rgba.length; i += 4) {
const r = rgba[i];
const g = rgba[i + 1];
const b = rgba[i + 2];
const gray = (r + g + b) / 3;
rgba[i] = gray; // Red
rgba[i + 1] = gray; // Green
rgba[i + 2] = gray; // Blue
}
// μμ λ λ°μ΄ν°λ‘ μ VideoFrame μμ±
const newFrame = new VideoFrame(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: videoFrame.timestamp,
duration: videoFrame.duration
});
videoFrame.close(); // μλ³Έ νλ μ ν΄μ
return newFrame;
}
2. μ»΄ν¨ν° λΉμ μ ν리μΌμ΄μ
VideoFrame planeμ μ»΄ν¨ν° λΉμ μμ
μ νμν ν½μ
λ°μ΄ν°μ μ§μ μ μΈ μ‘μΈμ€λ₯Ό μ 곡ν©λλ€. μ΄ λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ κ°μ²΄ κ°μ§, μΌκ΅΄ μΈμ, λͺ¨μ
μΆμ λ±μ μν μκ³ λ¦¬μ¦μ ꡬνν μ μμ΅λλ€. μ½λμ μ±λ₯μ΄ μ€μν λΆλΆμλ WebAssemblyλ₯Ό νμ©ν μ μμ΅λλ€.
μλ₯Ό λ€μ΄, μ»¬λ¬ VideoFrameμ νλ°±μΌλ‘ λ³νν λ€μ κ°μ₯μ리 κ°μ§ μκ³ λ¦¬μ¦(μ: μ벨 μ°μ°μ)μ μ μ©νμ¬ μ΄λ―Έμ§μ κ°μ₯μ리λ₯Ό μλ³ν μ μμ΅λλ€. μ΄λ κ°μ²΄ μΈμμ μν μ μ²λ¦¬ λ¨κ³λ‘ μ¬μ©λ μ μμ΅λλ€.
3. λΉλμ€ νΈμ§ λ° ν©μ±
VideoFrame planeμ μ¬μ©νμ¬ μλ₯΄κΈ°, ν¬κΈ° μ‘°μ , νμ λ° ν©μ±κ³Ό κ°μ λΉλμ€ νΈμ§ κΈ°λ₯μ ꡬνν μ μμ΅λλ€. ν½μ
λ°μ΄ν°λ₯Ό μ§μ μ‘°μνμ¬ μ¬μ©μ μ§μ μ ν λ° ν¨κ³Όλ₯Ό λ§λ€ μ μμ΅λλ€.
μλ₯Ό λ€μ΄, ν½μ
λ°μ΄ν°μ μΌλΆλ§ μ VideoFrameμΌλ‘ 볡μ¬νμ¬ VideoFrameμ μλ₯Ό μ μμ΅λλ€. μ΄λ₯Ό μν΄ layoutμ μ€νμ
κ³Ό μ€νΈλΌμ΄λλ₯Ό μ μ ν μ‘°μ ν΄μΌ ν©λλ€.
4. μ¬μ©μ μ§μ μ½λ± λ° νΈλμ€μ½λ©
WebCodecsλ AV1, VP9, H.264μ κ°μ μΌλ°μ μΈ μ½λ±μ κΈ°λ³Έμ μΌλ‘ μ§μνμ§λ§, μ΄λ₯Ό μ¬μ©νμ¬ μ¬μ©μ μ§μ μ½λ±μ΄λ νΈλμ€μ½λ© νμ΄νλΌμΈμ ꡬνν μλ μμ΅λλ€. μΈμ½λ© λ° λμ½λ© νλ‘μΈμ€λ₯Ό μ§μ μ²λ¦¬ν΄μΌ νμ§λ§, VideoFrame planeμ μ¬μ©νλ©΄ μμ ν½μ
λ°μ΄ν°μ μ‘μΈμ€νκ³ μ‘°μν μ μμ΅λλ€. μ΄λ νΉμ λΉλμ€ ν¬λ§·μ΄λ νΉμν μΈμ½λ© μꡬ μ¬νμ μ μ©ν μ μμ΅λλ€.
5. κ³ κΈ λΆμ
κΈ°λ³Έ ν½μ λ°μ΄ν°μ μ‘μΈμ€ν¨μΌλ‘μ¨ λΉλμ€ μ½ν μΈ μ λν μ¬μΈ΅ λΆμμ μνν μ μμ΅λλ€. μ¬κΈ°μλ μ₯λ©΄μ νκ· λ°κΈ° μΈ‘μ , μ£Όμ μμ μλ³ λλ μ₯λ©΄ λ΄μ©μ λ³ν κ°μ§μ κ°μ μμ μ΄ ν¬ν¨λ©λλ€. μ΄λ₯Ό ν΅ν΄ 보μ, κ°μ λλ μ½ν μΈ λΆμμ μν κ³ κΈ λΉλμ€ λΆμ μ ν리μΌμ΄μ μ ꡬνν μ μμ΅λλ€.
Canvas λ° WebGL μμ
VideoFrame planeμ ν½μ
λ°μ΄ν°λ₯Ό μ§μ μ‘°μν μ μμ§λ§, μ’
μ’
κ²°κ³Όλ₯Ό νλ©΄μ λ λλ§ν΄μΌ ν©λλ€. CanvasImageBitmap μΈν°νμ΄μ€λ VideoFrameκ³Ό <canvas> μμ μ¬μ΄μ λ€λ¦¬ μν μ ν©λλ€. VideoFrameμμ CanvasImageBitmapμ μμ±ν λ€μ drawImage() λ©μλλ₯Ό μ¬μ©νμ¬ μΊλ²μ€μ 그릴 μ μμ΅λλ€.
async function renderVideoFrameToCanvas(videoFrame, canvas) {
const bitmap = await createImageBitmap(videoFrame);
const ctx = canvas.getContext('2d');
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
bitmap.close(); // λΉνΈλ§΅ 리μμ€ ν΄μ
videoFrame.close(); // VideoFrame 리μμ€ ν΄μ
}
λ κ³ κΈ λ λλ§μ μν΄μλ WebGLμ μ¬μ©ν μ μμ΅λλ€. VideoFrame planeμ ν½μ
λ°μ΄ν°λ₯Ό WebGL ν
μ€μ²μ μ
λ‘λν λ€μ μ
°μ΄λλ₯Ό μ¬μ©νμ¬ ν¨κ³Όμ λ³νμ μ μ©ν μ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄ κ³ μ±λ₯ λΉλμ€ μ²λ¦¬μ GPUλ₯Ό νμ©ν μ μμ΅λλ€.
μ±λ₯ κ³ λ € μ¬ν
μμ ν½μ λ°μ΄ν°λ₯Ό λ€λ£¨λ κ²μ κ³μ° μ§μ½μ μΌ μ μμΌλ―λ‘ μ±λ₯ μ΅μ νλ₯Ό κ³ λ €νλ κ²μ΄ μ€μν©λλ€. λ€μμ λͺ κ°μ§ νμ λλ€:
- λ³΅μ¬ μ΅μν: λΆνμν ν½μ λ°μ΄ν° 볡μ¬λ₯Ό νΌνμμμ€. κ°λ₯νλ©΄ μ μ리(in-place)μμ μμ μ μννλλ‘ λ Έλ ₯νμμμ€.
- WebAssembly μ¬μ©: μ½λμ μ±λ₯μ΄ μ€μν λΆλΆμλ WebAssembly μ¬μ©μ κ³ λ €νμμμ€. WebAssemblyλ κ³μ° μ§μ½μ μΈ μμ μ λν΄ λ€μ΄ν°λΈμ κ°κΉμ΄ μ±λ₯μ μ 곡ν μ μμ΅λλ€.
- λ©λͺ¨λ¦¬ λ μ΄μμ μ΅μ ν: μ ν리μΌμ΄μ μ μ ν©ν ν½μ ν¬λ§·κ³Ό λ©λͺ¨λ¦¬ λ μ΄μμμ μ ννμμμ€. κ°λ³ μμ κ΅¬μ± μμμ μμ£Ό μ‘μΈμ€ν νμκ° μλ€λ©΄ μμΆ(packed) ν¬λ§·(μ: RGBA) μ¬μ©μ κ³ λ €νμμμ€.
- OffscreenCanvas μ¬μ©: λ°±κ·ΈλΌμ΄λ μ²λ¦¬λ₯Ό μν΄
OffscreenCanvasλ₯Ό μ¬μ©νμ¬ λ©μΈ μ€λ λλ₯Ό μ°¨λ¨νμ§ μλλ‘ νμμμ€. - μ½λ νλ‘νμΌλ§: λΈλΌμ°μ κ°λ°μ λꡬλ₯Ό μ¬μ©νμ¬ μ½λλ₯Ό νλ‘νμΌλ§νκ³ μ±λ₯ λ³λͺ© νμμ μλ³νμμμ€.
λΈλΌμ°μ νΈνμ±
WebCodecs λ° VideoFrame APIλ Chrome, Firefox, Safariλ₯Ό ν¬ν¨ν λλΆλΆμ μ΅μ λΈλΌμ°μ μμ μ§μλ©λλ€. κ·Έλ¬λ μ§μ μμ€μ λΈλΌμ°μ λ²μ λ° μ΄μ 체μ μ λ°λΌ λ€λ₯Ό μ μμ΅λλ€. MDN Web Docsμ κ°μ μ¬μ΄νΈμμ μ΅μ λΈλΌμ°μ νΈνμ± νλ₯Ό νμΈνμ¬ μ¬μ©νλ €λ κΈ°λ₯μ΄ λμ λΈλΌμ°μ μμ μ§μλλμ§ νμΈνμμμ€. ν¬λ‘μ€ λΈλΌμ°μ νΈνμ±μ μν΄μλ κΈ°λ₯ κ°μ§(feature detection)κ° κΆμ₯λ©λλ€.
μΌλ°μ μΈ ν¨μ κ³Ό λ¬Έμ ν΄κ²°
VideoFrame plane μμ
μ νΌν΄μΌ ν λͺ κ°μ§ μΌλ°μ μΈ ν¨μ μ λ€μκ³Ό κ°μ΅λλ€:
- μλͺ»λ λ μ΄μμ:
layoutλ°°μ΄μ΄ ν½μ λ°μ΄ν°μ λ©λͺ¨λ¦¬ λ μ΄μμμ μ ννκ² μ€λͺ νλμ§ νμΈνμμμ€. μλͺ»λ μ€νμ μ΄λ μ€νΈλΌμ΄λλ μμλ μ΄λ―Έμ§λ‘ μ΄μ΄μ§ μ μμ΅λλ€. - μΌμΉνμ§ μλ ν½μ
ν¬λ§·:
copyToλ©μλμ μ§μ νλ ν½μ ν¬λ§·μ΄VideoFrameμ μ€μ ν¬λ§·κ³Ό μΌμΉνλμ§ νμΈνμμμ€. - λ©λͺ¨λ¦¬ λμ:
VideoFrameλ°CanvasImageBitmapκ°μ²΄ μ¬μ©μ΄ λλλ©΄ νμ λ«μμ κΈ°λ³Έ 리μμ€λ₯Ό ν΄μ νμμμ€. κ·Έλ κ² νμ§ μμΌλ©΄ λ©λͺ¨λ¦¬ λμκ° λ°μν μ μμ΅λλ€. - λΉλκΈ° μμ
:
copyToλ λΉλκΈ° μμ μμ κΈ°μ΅νμμμ€. ν½μ λ°μ΄ν°μ μ‘μΈμ€νκΈ° μ μ λ³΅μ¬ μμ μ΄ μλ£λλλ‘awaitλ₯Ό μ¬μ©νμμμ€. - 보μ μ ν: λ€λ₯Έ μΆμ²(cross-origin)μ λΉλμ€μμ ν½μ λ°μ΄ν°μ μ‘μΈμ€ν λ μ μ©λ μ μλ 보μ μ νμ μ μνμμμ€.
μμ : YUVμμ RGBλ‘ λ³ν
λ 볡μ‘ν μλ‘, YUV420 VideoFrameμ RGB VideoFrameμΌλ‘ λ³ννλ κ²μ κ³ λ €ν΄ λ³΄κ² μ΅λλ€. μ΄λ Y, U, V planeμ μ½κ³ μ΄λ₯Ό RGB κ°μΌλ‘ λ³νν λ€μ μλ‘μ΄ RGB VideoFrameμ μμ±νλ κ³Όμ μ ν¬ν¨ν©λλ€.
μ΄ λ³νμ λ€μ 곡μμ μ¬μ©νμ¬ κ΅¬νν μ μμ΅λλ€:
R = Y + 1.402 * (Cr - 128)
G = Y - 0.34414 * (Cb - 128) - 0.71414 * (Cr - 128)
B = Y + 1.772 * (Cb - 128)
μ½λλ λ€μκ³Ό κ°μ΅λλ€:
async function convertYUV420ToRGBA(videoFrame) {
const width = videoFrame.codedWidth;
const height = videoFrame.codedHeight;
const yPlaneSize = width * height;
const uvPlaneSize = width * height / 4;
const yuvBuffer = new ArrayBuffer(yPlaneSize + 2 * uvPlaneSize);
const yuvPlanes = new Uint8ClampedArray(yuvBuffer);
const layout = [
{ offset: 0, stride: width }, // Y plane
{ offset: yPlaneSize, stride: width / 2 }, // U plane
{ offset: yPlaneSize + uvPlaneSize, stride: width / 2 } // V plane
];
await videoFrame.copyTo(yuvPlanes, {
format: 'I420',
codedWidth: width,
codedHeight: height,
layout: layout
});
const rgbaBuffer = new ArrayBuffer(width * height * 4);
const rgba = new Uint8ClampedArray(rgbaBuffer);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const yIndex = y * width + x;
const uIndex = Math.floor(y / 2) * (width / 2) + Math.floor(x / 2) + yPlaneSize;
const vIndex = Math.floor(y / 2) * (width / 2) + Math.floor(x / 2) + yPlaneSize + uvPlaneSize;
const Y = yuvPlanes[yIndex];
const U = yuvPlanes[uIndex] - 128;
const V = yuvPlanes[vIndex] - 128;
let R = Y + 1.402 * V;
let G = Y - 0.34414 * U - 0.71414 * V;
let B = Y + 1.772 * U;
R = Math.max(0, Math.min(255, R));
G = Math.max(0, Math.min(255, G));
B = Math.max(0, Math.min(255, B));
const rgbaIndex = y * width * 4 + x * 4;
rgba[rgbaIndex] = R;
rgba[rgbaIndex + 1] = G;
rgba[rgbaIndex + 2] = B;
rgba[rgbaIndex + 3] = 255; // Alpha
}
}
const newFrame = new VideoFrame(rgba, {
format: 'RGBA',
codedWidth: width,
codedHeight: height,
timestamp: videoFrame.timestamp,
duration: videoFrame.duration
});
videoFrame.close(); // μλ³Έ νλ μ ν΄μ
return newFrame;
}
μ΄ μμ λ VideoFrame plane μμ
μ κ°λ ₯ν¨κ³Ό 볡μ‘μ±μ 보μ¬μ€λλ€. μ΄λ₯Ό μν΄μλ ν½μ
ν¬λ§·, λ©λͺ¨λ¦¬ λ μ΄μμ λ° μ κ³΅κ° λ³νμ λν μΆ©λΆν μ΄ν΄κ° νμν©λλ€.
κ²°λ‘
WebCodecsμ VideoFrame plane APIλ λΈλΌμ°μ μμ λΉλμ€ μ²λ¦¬μ λν μλ‘μ΄ μ°¨μμ μ μ΄κΆμ μ 곡ν©λλ€. ν½μ
λ°μ΄ν°μ μ§μ μ‘μΈμ€νκ³ μ‘°μνλ λ°©λ²μ μ΄ν΄ν¨μΌλ‘μ¨ μ€μκ° λΉλμ€ ν¨κ³Ό, μ»΄ν¨ν° λΉμ , λΉλμ€ νΈμ§ λ±μ μν κ³ κΈ μ ν리μΌμ΄μ
μ λ§λ€ μ μμ΅λλ€. VideoFrame plane μμ
μ΄ μ΄λ €μΈ μ μμ§λ§, μ μ¬μ μΈ λ³΄μμ μλΉν©λλ€. WebCodecsκ° κ³μ λ°μ ν¨μ λ°λΌ, λ―Έλμ΄λ₯Ό λ€λ£¨λ μΉ κ°λ°μμκ² νμμ μΈ λκ΅¬κ° λ κ²μ
λλ€.
VideoFrame plane APIλ₯Ό μ€ννκ³ κ·Έ κΈ°λ₯μ νμν΄ λ³΄μκΈ°λ₯Ό κΆμ₯ν©λλ€. κΈ°λ³Έ μ리λ₯Ό μ΄ν΄νκ³ λͺ¨λ² μ¬λ‘λ₯Ό μ μ©ν¨μΌλ‘μ¨ λΈλΌμ°μ μμ κ°λ₯ν κ²μ κ²½κ³λ₯Ό λνλ νμ μ μ΄κ³ μ±λ₯μ΄ λ°μ΄λ λΉλμ€ μ ν리μΌμ΄μ
μ λ§λ€ μ μμ΅λλ€.
μΆκ° νμ΅ μλ£
- MDN Web Docsμ WebCodecs
- WebCodecs μ¬μ
- GitHubμ WebCodecs μν μ½λ μ μ₯μ.